#include "BattleSimulator.h"
using namespace std;
using namespace BWAPI;

BattleSimulator::BattleSimulator(Strategy* player, Strategy* enemy)
{
	strats[RED] = player;
	strats[BLUE] = enemy;

	unitStats[RED] = NULL;
	unitStats[BLUE] = NULL;

	setSide(true);
	setSide(false);

	battleOver = false;
}

BattleSimulator::~BattleSimulator(void)
{
	deleteUnitStats(RED);
	deleteUnitStats(BLUE);
}

void BattleSimulator::setSide(bool isRed){
	map<string, vector<Unit*>*> force;
	Side side = isRed? RED: BLUE;
	if(isRed){
		force = StarUtil::getPlayerGroups();
		block[side] = -BASE_RANGE;
	}else{
		force = StarUtil::getEnemyGroups();
		block[side] = BASE_RANGE;
	}
								  
	deleteUnitStats(side);
	unitStats[side] = new map<string, vector<UnitStats*>*>();
	
	//for every group of units in the force
	for(map<string, vector<Unit*>*>::iterator unitGroup = force.begin();
		unitGroup != force.end(); unitGroup++)
	{
		positions[side][(*unitGroup).first] = block[side];
		vector<Unit*>* ug = (*unitGroup).second;
		vector<UnitStats*>* statGroup = new vector<UnitStats*>;
		if(ug == NULL){
			return;
		}
		//for every unit in the group
		for(vector<Unit*>::iterator unit = ug->begin();
			unit != ug->end(); unit++){
			statGroup->push_back(new UnitStats());
			statGroup->back()->shields = (*unit)->getShields();
			statGroup->back()->hp = (*unit)->getHitPoints();
			statGroup->back()->dead = (*unit)->getHitPoints() == 0;
			statGroup->back()->type = (*unitGroup).first;
			statGroup->back()->nextAttack = 0;
			statGroup->back()->target = NULL;	
		}
		string type = (*unitGroup).first;
		(*unitStats[side])[type] = statGroup;
	}
}

void BattleSimulator::deleteUnitStats(Side side){
	if(unitStats[side] != NULL){
		for(map<string, vector<UnitStats*>*>::iterator group = 
			unitStats[side]->begin();
			group != unitStats[side]->end(); group++)
		{
			for(vector<UnitStats*>::iterator it = (*group).second->begin();
				it != (*group).second->end(); it++)
			{
				delete (*it);
			}		
		}
		delete unitStats[side];
		unitStats[side] = NULL;
	}
}

void BattleSimulator::step(){
	
	
    stepSide(RED);
	stepSide(BLUE);

	checkDead(RED);
	checkDead(BLUE);
}

void BattleSimulator::resolve(){
	int counter = 1000;
	while(!battleOver && counter){
		step();
		counter--;
	}
}

void BattleSimulator::checkDead(Side side){
	bool someAlive = false;
	for(map<string, vector<UnitStats*>*>::iterator it = unitStats[side]->begin();
		it != unitStats[side]->end(); it++)
	{
		for(vector<UnitStats*>::iterator unit = (*it).second->begin();
			unit != (*it).second->end(); unit++)
		{
			if((*unit)->hp <= 0){
				(*unit)->dead = true;
			}else{		
				someAlive = true;
			}
		}	
	}
	if(!someAlive){
		battleOver = true;
		redWonBattle = side == BLUE;
	}
}

bool BattleSimulator::redWon(){
	return redWonBattle;
}

map<string, vector<BattleSimulator::UnitStats*>*>* 
	BattleSimulator::getRemainingUnits(bool wantRed)
{
	Side side = wantRed? RED:BLUE;
	return unitStats[side];
}

void BattleSimulator::stepSide(Side side){

	
	//For every unit stats
	Side other = side == RED? BLUE:RED;
	int direction = side == RED? 1: -1;
	for(map<string, vector<UnitStats*>*>::iterator myType = 
		unitStats[side]->begin();
		myType != unitStats[side]->end(); myType++)
	{
		vector<string> targetPriority = 
			strats[side]->getTargetPriority((*myType).first);
		//find viable target type
		string targetType = "none";
		for(vector<string>::iterator enemyType = targetPriority.begin(); 
			enemyType!= targetPriority.end(); enemyType++)
		{
			WeaponType weapon = StarUtil::getWeapon(*enemyType,(*myType).first);
			if(weapon.getName() == "None")
			{
				//unit cannot attack this type, go to next unit
				continue;
				
	
			} 

			vector<UnitStats*>* targets = (*unitStats[other])[*enemyType];
			bool found = false;
			for(vector<UnitStats*>::iterator target = targets->begin();
				target != targets->end(); target++)
			{
				if(!(*target)->dead){
					found = true;
					break;
				}
			}
			if(found){
				targetType = *enemyType;
				break;
			}
		}

		
		if(targetType != "none"){ 
			int maxRange = StarUtil::
				getWeapon((*myType).first, targetType).maxRange();
			int range = getRange(side, (*myType).first, targetType);
			if(getRange(side, (*myType).first, targetType) >= maxRange){
				
				advance(side, (*myType).first, (range - maxRange) + 1);
				targetType = "none";
			}
		}

		for(vector<UnitStats*>::iterator myUnit = (*myType).second->begin();
			myUnit != (*myType).second->end(); myUnit++)
		{
			//check for dead
			if((*myUnit)->dead)
				continue;
			
			//if this unit does not have a target
			if((*myUnit)->target == NULL || (*myUnit)->target->dead){
				assignTarget((*myUnit), targetType, other);
				
			}
			
			doAttack((*myUnit));
			
		}
	}
}

void BattleSimulator::assignTarget(UnitStats* unit, 
								   const string& targetType, 
								   const Side& enemy)
{
	
	if(targetType == "none")
		return;

	
	map<string, vector<UnitStats*>*> map = *unitStats[enemy];
	vector<UnitStats*>* enemies = map[targetType];


	//for now, attacks first available enemy
	for(vector<UnitStats*>::iterator enemy = enemies->begin();
		enemy != enemies->end(); enemy++)
	{
		if(!(*enemy)->dead){
				
			//Broodwar->printf("target assigned %d", unit->target);
			unit->target = *enemy;
			if(StarUtil::isMelee(UnitTypes::getUnitType(unit->type))){
				//Broodwar->printf(unit->type.c_str());
				unit->nextAttack += MELEE_PENALTY;
				int max = MELEE_PENALTY + UnitTypes::
					getUnitType(unit->type).groundWeapon().damageCooldown();
				unit->nextAttack = unit->nextAttack > max? max:unit->nextAttack;
			}
		}
	}
}



void BattleSimulator::doAttack(UnitStats* unit){
	if(unit->type == "Zerg Zergling"){
			Broodwar->printf("attacking");
		Broodwar->printf("target is %d and next attack is %d",unit->target,
			unit->nextAttack);
	
	}
	if(unit->target != NULL && unit->nextAttack <= 0){
		
		WeaponType weapon = StarUtil::getWeapon(unit->type, unit->target->type);
		UnitType attacker = UnitTypes::getUnitType(unit->type);
		UnitType enemy = UnitTypes::getUnitType(unit->target->type);
		double damage = weapon.damageAmount();
		if(unit->target->shields <= 0){
			damage -= enemy.armor();
			damage = damage < 0.5? 0.5: damage;
		}
		
		string damageType = weapon.damageType().getName();
		string enemySize = enemy.size().getName();
		if(unit->target->shields < damage){
			//No modifiers
		}else if(damageType == CONCUSSIVE){
			if(enemySize == MEDIUM)
				damage *= 0.5;
			else if(enemySize == LARGE)
				damage *= 0.25;
		} else if(damageType == EXPLOSIVE){
			if(enemySize == MEDIUM)
				damage *= 0.75;
			else if(enemySize == SMALL)
				damage *= 0.50;
		}
		
		if(unit->target->shields < damage){
			damage -= unit->target->shields;
			unit->target->shields = 0;
	
			unit->target->hp -= damage;
			totalDamageDealt += damage;
		} else{
			unit->target->shields -= damage;
		} 
		
		unit->nextAttack += weapon.damageCooldown();
		
	}
	if(unit->nextAttack > 0){
		unit->nextAttack -= BS_TICK;
	}
	
}

bool BattleSimulator::isDone(){
	return battleOver;

}


int BattleSimulator::getRange(Side attacker,const string& unit,
							  const string& target){
	int multiplier = attacker == RED? -1:1;
	Side other = attacker == RED? BLUE:RED;
	int position1 = positions[attacker][unit];
	int position2 = positions[other][target];

	int distance = position1 - position2;
	distance =  distance * multiplier;

	return distance;
}

void BattleSimulator::advance(Side side,const string& type, int limit){
	int multiplier = side == RED? 1: -1;
	int movement = limit < SPEED ? limit: SPEED;
	movement *= multiplier;
	positions[side][type] += movement;
	if(positions[side][type] * multiplier < block[side]*multiplier){
		block[side] = positions[side][type];
	}
}

void BattleSimulator::printSideStats(bool printRed){
	Side side = printRed? RED: BLUE;
	map<string, vector<BattleSimulator::UnitStats*>*>* units = 
		getRemainingUnits(printRed);
		
	
	for(map<string, vector<BattleSimulator::UnitStats*>*>::iterator unitGroup =
		units->begin();unitGroup != units->end(); unitGroup++)
	{
		for(vector<BattleSimulator::UnitStats*>::iterator u = 
			(*unitGroup).second->begin();
			u != (*unitGroup).second->end(); u++)
		{
			BattleSimulator::UnitStats* stats = *u;
			UnitType type = UnitTypes::getUnitType(stats->type);
			Broodwar->printf(
				"type %s, hp %g/%d, shields %g/%d, position %d, target %d", 
							 stats->type.c_str() , 
							 stats->hp,
							 type.maxHitPoints(),
							 stats->shields,
							 type.maxShields(),
							 positions[side][stats->type],
							 stats->target);
		}
		
	}


}